#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>

#include "Util.h"

#define NUM_SEQUENTIAL_READS 3
#define NUM_SECONDS_TO_SLEEP 5

typedef enum {
	SEQUENTIAL, RANDOMOFFSET_SEQUENTIAL, RANDOM
} readType_t;

struct workLoadParams {
	int option;
	char *buffer;
	long bufferSize;
	long numReads;
};

void doSequentialReads(char *buffer, long bufferSize, long numReads);
void doRandOffsetAndSequentialReads(char *buffer, long bufferSize,
		long numReads);
void doRandomReads(char *buffer, long bufferSize, long numReads);
void* runWorkload(void* ptr);
void* fillUpResidentSet(void *ptr);

struct workLoadParams* constructReadParams(int option, char *buffer, long bufferSize, long numReads);

int main(int argc, char *argv[]) {
	long bufferSize, numReads;
	int option = 0;
	char *buffer = NULL;
	struct timespec startTime, endTime;
	double timeToDoMalloc = 0;
	readType_t readType;
    struct workLoadParams* params;

    pthread_t readThread, tlbfillUpThread;

	validateAndFill2InputArgumentsWithOption(argc, argv, &bufferSize,
			"BufferSize", &numReads, "NumReads", &option, "Option");
	readType = option;


	srand(time(NULL ));
	clock_gettime(CLOCK_REALTIME, &startTime);
	buffer = (char*) malloc(sizeof(char) * bufferSize);
	if (buffer == NULL ) {
		perror("Error while allocating memory to buffer\n");
		return EXIT_FAILURE;
	} else {
		fillAllocatedMemory(buffer, bufferSize);
	}
	clock_gettime(CLOCK_REALTIME, &endTime);
	timeToDoMalloc = getDiffInNanoSeconds(startTime, endTime);

	params = constructReadParams(option, buffer, bufferSize, numReads);

	pthread_create( &readThread, NULL, runWorkload, (void*) params);
	pthread_create( &tlbfillUpThread, NULL, fillUpResidentSet, NULL);

	pthread_join( tlbfillUpThread, NULL);
	pthread_join( readThread, NULL);

	free(buffer);

	return EXIT_SUCCESS;
}

void* runWorkload(void* ptr) {
    double timeForReads;
    struct timespec startTime, endTime;
    struct workLoadParams* param = (struct workLoadParams*) ptr;

    readType_t readType = param->option;
    char *buffer = param->buffer;
    long bufferSize = param->bufferSize;
    long numReads = param->numReads;

    sched_yield();
    sleep(NUM_SECONDS_TO_SLEEP);
    sched_yield();

	clock_gettime(CLOCK_REALTIME, &startTime);
	switch (readType) {
	case SEQUENTIAL:
		doSequentialReads(buffer, bufferSize, numReads);
		break;
	case RANDOMOFFSET_SEQUENTIAL:
		doRandOffsetAndSequentialReads(buffer, bufferSize, numReads);
		break;
	case RANDOM:
		doRandomReads(buffer, bufferSize, numReads);
		break;
	}
	clock_gettime(CLOCK_REALTIME, &endTime);
	timeForReads = getDiffInNanoSeconds(startTime, endTime);

	printf("%f %f\n", (double) bufferSize / ONE_MB,  (double) timeForReads / (double) numReads);
	return NULL;
}

void* fillUpResidentSet(void *ptr) {
	long bufferSize = ONE_MB * 500;
	char *buffer = (char*) malloc(sizeof(char) * bufferSize);

	if (buffer == NULL ) {
		perror("Error while allocating memory to buffer\n");
		return NULL;
	} else {
		fillAllocatedMemory(buffer, bufferSize);
	}
	return NULL ;
}

struct workLoadParams* constructReadParams(int option, char *buffer, long bufferSize, long numReads) {
	struct workLoadParams *param = (struct workLoadParams*) malloc(sizeof(struct workLoadParams));
	param->option = option;
	param->buffer = buffer;
	param->bufferSize = bufferSize;
	param->numReads = numReads;
	return param;
}

void doSequentialReads(char *buffer, long bufferSize, long numReads) {
	long offset = 0, nextOffset = 0;
	int counter = 0, INCREMENT = 1;
	struct timespec startTime, endTime;
	char *log = malloc(10000);
	char tempLog[30],c;
    double timeForRead = 0;

	offset = rand() % bufferSize;
	for (counter = 0; counter < numReads; counter++) {
		nextOffset = getNextOffsetWithinPage(offset, INCREMENT, bufferSize);

		clock_gettime(CLOCK_REALTIME, &startTime);
		//buffer[offset] += 'a';
		c = buffer[offset];
		clock_gettime(CLOCK_REALTIME, &endTime);
		timeForRead = getDiffInNanoSeconds(startTime, endTime);
        sprintf(tempLog,"%d\t%f\n",counter,timeForRead);
        strcat(log, tempLog);

		offset = nextOffset;
	}

	puts(log);
}

void doRandOffsetAndSequentialReads(char *buffer, long bufferSize,
		long numReads) {
	long numRandOffsets = numReads / NUM_SEQUENTIAL_READS, offset = 0,
			nextOffset = 0;
	int counter = 0, INCREMENT = 1, i = 0;
	struct timespec startTime, endTime;
	char *log = malloc(10000);
	char tempLog[30], c;
	double timeForRead = 0;

	for (counter = 0; counter < numRandOffsets; counter++) {
		offset = rand() % bufferSize;
		for (i = 0; i < NUM_SEQUENTIAL_READS; i++) {
			nextOffset = getNextOffsetWithinPage(offset, INCREMENT, bufferSize);

			clock_gettime(CLOCK_REALTIME, &startTime);
			//buffer[offset] += 'a';
			c = buffer[offset];
			clock_gettime(CLOCK_REALTIME, &endTime);
			timeForRead = getDiffInNanoSeconds(startTime, endTime);
			sprintf(tempLog, "%d\t%f\n", counter, timeForRead);
			strcat(log, tempLog);

			offset = nextOffset;
		}
	}

	puts(log);
}

void doRandomReads(char *buffer, long bufferSize, long numReads) {
	long offset = 0, nextOffset;
	int counter = 0, randIncrement = 0;
	struct timespec startTime, endTime;
	char *log = malloc(10000);
	char tempLog[30], c;
	double timeForRead = 0;

	for (counter = 0; counter < numReads; counter++) {
		offset = rand() % bufferSize;

		clock_gettime(CLOCK_REALTIME, &startTime);
		//buffer[offset] += 'a';
		c = buffer[offset];
		clock_gettime(CLOCK_REALTIME, &endTime);
		timeForRead = getDiffInNanoSeconds(startTime, endTime);
		sprintf(tempLog, "%d\t%f\n", counter, timeForRead);
		strcat(log, tempLog);
	}

	puts(log);
}
